﻿//////////////////////////////////////////////////////////////////
//
// Gramp.cs
//
// Copyright (c) 2012 Dan Pike.
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation files
// (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software,
// and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using Irony.Parsing;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Utilities;
using Irony.Ast;
using Irony.Interpreter.Ast;

namespace ScriptIrony
{
   public class Gramp : Grammar
   {
      private static Logger logger_;

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      public Gramp()
         : base(true)
      {
         try
         {
            logger_ = Program.logger;

            this.LanguageFlags |= LanguageFlags.NewLineBeforeEOF | LanguageFlags.CreateAst | LanguageFlags.SupportsBigInt;

            // Ignore comments
            CommentTerminal blockComment = new CommentTerminal("block-comment", "/*", "*/");
            CommentTerminal lineComment = new CommentTerminal("line-comment", "//",
                "\r", "\n", "\u2085", "\u2028", "\u2029");
            NonGrammarTerminals.Add(blockComment);
            NonGrammarTerminals.Add(lineComment);

            // Non-terminals in our script language
            NonTerminal script = new NonTerminal("script");
            
            NonTerminal declarations = new NonTerminal("declarations");
            NonTerminal declaration = new NonTerminal("declaration");
            NonTerminal parenParameters = new NonTerminal("paren-parameters");
            NonTerminal parameters = new NonTerminal("parameters");
            NonTerminal block = new NonTerminal("block");
            NonTerminal blockContent = new NonTerminal("block-content");
            NonTerminal statement = new NonTerminal("statement");
            NonTerminal parenExpressionAlways = new NonTerminal("paren-expression-always");
            NonTerminal parenExpression = new NonTerminal("paren-expression");
            NonTerminal forHeader = new NonTerminal("for-header");
            NonTerminal forBlock = new NonTerminal("for-block");
            NonTerminal assignExpression = new NonTerminal("assign-expression");
            NonTerminal expression = new NonTerminal("expression");
            NonTerminal booleanOperator = new NonTerminal("boolean-operator");
            NonTerminal relationalExpression = new NonTerminal("relational-expression");
            NonTerminal relationalOperator = new NonTerminal("relational-operator");
            NonTerminal bitExpression = new NonTerminal("bit-expression");
            NonTerminal bitOperator = new NonTerminal("bit-operator");
            NonTerminal addExpression = new NonTerminal("add-expression");
            NonTerminal addOperator = new NonTerminal("operator+-");
            NonTerminal multiplyExpression = new NonTerminal("multiply");
            NonTerminal multiplyOperator = new NonTerminal("operator*/");
            NonTerminal prefixExpression = new NonTerminal("prefix-expression");
            NonTerminal prefixOperator = new NonTerminal("prefix-operator");
            NonTerminal factor = new NonTerminal("factor");
            NonTerminal identifierExpression = new NonTerminal("identifier-expression");

            StringLiteral name = new StringLiteral("string", "\"", StringOptions.AllowsAllEscapes);
            IdentifierTerminal identifier = new IdentifierTerminal("identifier");
            NumberLiteral number = new NumberLiteral("number",
               NumberOptions.AllowStartEndDot | NumberOptions.AllowLetterAfter | NumberOptions.AllowSign);
            number.DecimalSeparator = '.';
            number.AddPrefix("0x", NumberOptions.Hex);
            number.AddSuffix("d", TypeCode.Double);
            number.DefaultFloatType = TypeCode.Single;

            this.RegisterBracePair("{", "}");
            this.RegisterBracePair("(", ")");
            this.MarkTransient(
               addExpression,
               bitExpression,
               block,
               blockContent,
               declarations,
               declaration,
               expression,
               multiplyExpression,
               parenParameters,
               prefixExpression,
               relationalExpression
               );

            // Now the rules
            script.Rule
               = declarations^4;

            declarations.Rule
               = MakeStarRule(declarations, declaration);

            declaration.Rule
                = identifier + "(" + parenParameters + block;

            parenParameters.Rule
                = ToTerm(")")
                | parameters + ")";

            parameters.Rule
                = parameters + "," + expression
                | expression;

            block.Rule
                = ToTerm("{") + "}"
                | "{" + blockContent + "}"
                | ";";

            blockContent.Rule
                = MakePlusRule(blockContent, statement);

            statement.Rule
                = declaration
                | "while" + parenExpressionAlways + block
                | "for" + forHeader + block
                | "if" + parenExpressionAlways + block
                | "if" + parenExpressionAlways + block + "else" + block
                | assignExpression
                | "return" + expression
                | "break"
                | "continue";

            parenExpressionAlways.Rule = parenExpression;

            parenExpression.Rule = ToTerm("(") + expression + ")";

            forHeader.Rule = "(" + forBlock + ")";

            forBlock.Rule = assignExpression + ";" + parameters + ";" + assignExpression;

            assignExpression.Rule
                = identifier + "=" + expression
                | expression;

            expression.Rule
                = expression + booleanOperator + relationalExpression
                | relationalExpression;

            booleanOperator.Rule
                = ToTerm("&&")
                | "||";

            relationalExpression.Rule
                = relationalExpression + relationalOperator + bitExpression
                | bitExpression;

            relationalOperator.Rule
                = ToTerm(">")
                | ">="
                | "<"
                | "<="
                | "=="
                | "!=";

            bitExpression.Rule
                = bitExpression + bitOperator + addExpression
                | addExpression;

            bitOperator.Rule
                = ToTerm("|")
                | "&"
                | "^";
#if true
            addExpression.Rule
                = addExpression + addOperator + multiplyExpression
                | multiplyExpression;

            addOperator.Rule
                = ToTerm("+") | "-";

            multiplyExpression.Rule
                = multiplyExpression + multiplyOperator + prefixExpression 
                | prefixExpression;

            multiplyOperator.Rule
                = ToTerm("*")
                | "/";
#else
            addExpression.Rule
                = addExpression + addOperator + prefixExpression
                | prefixExpression;

            addOperator.Rule
                = ToTerm("+") | "-" | "*" | "/";
#endif

            prefixExpression.Rule
                = prefixOperator + factor
                | factor;

            prefixOperator.Rule = ToTerm("!");

            factor.Rule
               = identifier
               | number
               | parenExpressionAlways
               | name;

            // Operator precedence
            RegisterOperators(10, "&&", "||");
            RegisterOperators(20, "==", "<", "<=", ">", ">=", "!=");
            RegisterOperators(25, "&", "|");
            RegisterOperators(30, "+", "-");
            RegisterOperators(40, "*", "/");
            RegisterOperators(50, "!");
            RegisterOperators(60, Associativity.Right, "**");

            this.Root = script;
            MarkPunctuation("(", ")", ";", "{", "}", ",");
            //, "&&", "||", "==", "<", "<=", ">", ">=", "!=", "&", "|", "+", "-", "*", "/", "!", "**");
         }
         catch (System.Exception ex)
         {
            logger_.exception(ex);
         }
      }
   }
}
